﻿// Inner Fire 游戏引擎库
// AnimationChannel - 动画通道
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 所属层级：应用层(4)
// 用于保存一个动画序列中的一个通道。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-03-17

#include "animation_channel.h"
#include <graphics/imath.h>
#include <game/animation/animation_clip.h>
#include <global_switch.h>
#include <klog.h>

using namespace DirectX;

namespace ifire::game {

DirectX::XMVECTOR AnimationVector::Interpolate(
    const AnimationVector& end_key, float factor) const {

  XMVECTOR start = XMLoadFloat3(&value);
  XMVECTOR end = XMLoadFloat3(&end_key.value);
  XMVECTOR delta = end - start;
  return start + factor * delta;
}

AnimationQuaternion::AnimationQuaternion(
    float anime_time, DirectX::XMFLOAT4 anime_value)
    : time(anime_time), value(anime_value) {}

DirectX::XMVECTOR AnimationQuaternion::Interpolate(
    const AnimationQuaternion& end_key, float factor) const {
  XMVECTOR start = XMLoadFloat4(&value);
  XMVECTOR end = XMLoadFloat4(&end_key.value);
  XMVECTOR result = XMQuaternionSlerp(start, end, factor);
  return result;
}

// 获得一个通道的插值（因为旋转是用的四元数，所以不能完全统一，没办法）
template <typename T>
DirectX::XMVECTOR GetInterpolated(
    float frame, float duration, const eastl::vector<T>& keys) {
  // 仅有一个项目的时候，直接返回
  if (keys.size() == 1) {
    return keys[0].GetValue();
  }

  // 这里的插件算法非常简单明快：
  // 1、查找到当前的索引。会根据当前的时间戳，去选择最接近的索引，其实等于一个对时间戳的floor操作。
  size_t index = FindFramePosition(frame, keys);
  size_t next = index + 1;
  assert(next < keys.size());
  // 2、得到两个索引的时间戳
  float t1 = (float)keys[index].time;
  float t2 = (float)keys[next].time;
  // 3、通过时间戳计算出当前时间应该在两个时间戳中的百分比。
  float delta_time = t2 - t1;
  // 如果帧数超过限制，通常表示这个通道中没有足够的帧数，那么便使用总的项目计算
  float factor = (frame - t1) / delta_time;
  if (!(factor >= 0.0f && factor <= 1.0f)) {
    factor = frame / duration;
    assert(factor >= 0.0f && factor <= 1.0f);
  }
  // 4、获得两个值，并根据百分比的进度进行插值。
  const T& start = keys[index];
  const T& end = keys[next];
  // 进行插值计算
  return start.Interpolate(end, factor);
}

template <typename T>
size_t FindFramePosition(float frame, const eastl::vector<T>& keys) {
  for (size_t i = 0; i < keys.size() - 1; i++) {
    float t = (float)keys[i + 1].time;
    if (frame <= t) {
      return i;
    }
  }

  return 0;
}

AnimationChannel::AnimationChannel(
    AnimationClip* parent, ikit::sstr channel_name)
    : parent_(parent), name(channel_name) {}

static bool print_anime_calc = false;

XMMATRIX AnimationChannel::CalcTransform(float frame) const {
  // 获得缩放矩阵
  XMVECTOR scale = CalcInterpolatedScaling(frame);
  XMMATRIX scale_mat = XMMatrixScalingFromVector(scale);

  // 获得旋转矩阵
  XMVECTOR rotation = CalcInterpolatedRotation(frame);
  XMMATRIX rotation_mat = XMMatrixRotationQuaternion(rotation);

  //  获得平移矩阵
  XMVECTOR translation = CalcInterpolatedPosition(frame);
  XMMATRIX translation_mat = XMMatrixTranslationFromVector(translation);


  // 组成矩阵
  return XMMatrixAffineTransformation(scale, XMVectorZero(), rotation, translation);
}

XMVECTOR AnimationChannel::CalcInterpolatedPosition(float frame) const {
  return GetInterpolated(frame, parent_->GetFrameCount(), position_keys);
}

XMVECTOR AnimationChannel::CalcInterpolatedRotation(float frame) const {
  return GetInterpolated(frame, parent_->GetFrameCount(), rotation_keys);
}

XMVECTOR AnimationChannel::CalcInterpolatedScaling(float frame) const {
  return GetInterpolated(frame, parent_->GetFrameCount(), scale_keys);
}

} // namespace ifire::game